/*
 * Created on Aug 4, 2006
 *
 */
package com.inibb.app.sftp;

import java.net.InetAddress;
import java.util.ArrayList;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.inibb.app.sftp.exception.SFTPConnectionPoolException;
import com.inibb.app.sftp.exception.SFTPException;
import com.inibb.app.sftp.exception.SFTPIOException;






/**
 * @author PCCW
 *
 */
public class SFTPConnectionPool {
	public static final int DEFAULT_MAX_TRY_COUNT = 2;
	public static final int DEFAULT_TRY_WAIT = 10000;
	private static final int DEFAULT_SSH_PORT=SFTPConnection.DEFAULT_SSH_PORT;
	private static final Logger LOG = LoggerFactory.getLogger(SFTPConnectionPool.class);

	private InetAddress addr=null;
	private SFTPAuthInfo auth=null;
	private boolean noneCipherMode = false;
	
	private int port=DEFAULT_SSH_PORT;
	private int poolSize=1;
	private String implClassName=null;
	private String name = null;
	private String nameMark = "";
	private int maxTryCount = DEFAULT_MAX_TRY_COUNT;
	private int tryWait = DEFAULT_TRY_WAIT;
	
	private int exCount = 0;
	private ArrayList connPool=new ArrayList();
	private long currConn=0;
	private SFTPConnectionFactory fac = null;
	private boolean inited = false;
	/**
	 * To initialize the Connection Pool
	 * @throws SFTPConnectionPoolException
	 */
	public void init() 
		throws SFTPConnectionPoolException	
	{
		if (implClassName!=null) {
			try {
				fac = SFTPConnectionFactory.getFactoryInstance(implClassName);
			} catch (ClassNotFoundException e) {
				throw new SFTPConnectionPoolException(SFTPConnectionPoolException.CODE_INIT,nameMark+"Error initialization SFTPConnectionPool",e);
			}
		} else {
			fac = SFTPConnectionFactory.getDefaultFactoryInstance();
		}
		
		disconnectAll();
		connPool.clear();
		
		for (int i=1;i<=poolSize;i++) {
			LOG.debug(nameMark+"Initializing connection #"+i);
			PooledSFTPConnection conn = createConnection(fac.getInstance(),addr,port,auth,i );
			connPool.add(conn);
		}
		
		try {
			connectAll() ;
		} catch (Exception e) {
			String msg = nameMark+"Error when establishing SFTPConnections during initialization - "+e;
			LOG.warn("SFTPPOOL: {0}",msg);

		}
		inited = true;
	}
	
	private PooledSFTPConnection createConnection(SFTPConnection realConn, InetAddress addr, int port, SFTPAuthInfo auth, int id) {
		realConn.setNoneCipherMode(noneCipherMode);
		PooledSFTPConnection conn = new PooledSFTPConnection(realConn,addr,port,auth );
		conn.setMaxTryCount(maxTryCount);
		conn.setTryWait(tryWait);
		conn.setExclusive(id<0);
		conn.setNameMark("["+name+":"+id+"]:");
		return conn;
	}

	/**
	 * To disconnect all the connections in the pool
	 *
	 */
	public void disconnectAll() {
		int size = connPool.size();
		for (int i=0;i<size;i++) {
			PooledSFTPConnection conn = getConn(i);
			LOG.debug("Disconnecting connection {0}", conn.getNameMark());
			conn.forceDisconnect();
		}
	}
	
	public synchronized void destroy() {
		disconnectAll();
		connPool.clear();
	}
	
	
	
	/**
	 * To connect all connections in the pool
	 * @throws SFTPException
	 */
	public void connectAll() 
		throws SFTPException
	{
		int size = connPool.size();
		for (int i=0;i<size;i++) {
			PooledSFTPConnection conn = getConn(i);
			conn.getRealConn().connect(addr,port,auth);
		}		
	}
	
	/**
	 * To obtain a SFTPConnection instance (non-exclusive, with auto-reconnect).
	 * @return
	 */
	public SFTPConnection getConnection() {
		return getConn(getCurrentIdx());
	}
	
	/**
	 * To obtain (create) an exclusive SFTPConnection instance for use (with auto-reconnect).
	 * Please disconnect the connection after use.
	 * @return
	 * @throws SFTPException
	 */
	public SFTPConnection getExclusiveConnection() 
		throws SFTPConnectionPoolException, SFTPIOException
	{
		LOG.debug(nameMark+"creating exclusive SFTPConnection");
		return createConnection(fac.getInstance(),addr,port,auth,--exCount );
	}
	
	private int getCurrentIdx() {
		return (int)((++currConn) % poolSize);
	}
	
	private PooledSFTPConnection getConn(int idx) {
		return (PooledSFTPConnection)connPool.get(idx);
	}

	
	
	/**
	 * @return Returns the addr.
	 */
	public InetAddress getAddr() {
		return addr;
	}
	/**
	 * @param addr The addr to set.
	 */
	public void setAddr(InetAddress addr) {
		if (!inited) this.addr = addr;
	}
	/**
	 * @return Returns the auth.
	 */
	public SFTPAuthInfo getAuth() {
		return auth;
	}
	/**
	 * @param auth The auth to set.
	 */
	public void setAuth(SFTPAuthInfo auth) {
		if (!inited) this.auth = auth;
	}
	/**
	 * @return Returns the poolSize.
	 */
	public int getPoolSize() {
		return poolSize;
	}
	/**
	 * @param poolSize The poolSize to set.
	 */
	public void setPoolSize(int poolSize) {
		if (!inited) this.poolSize = poolSize;
	}
	/**
	 * @return Returns the port.
	 */
	public int getPort() {
		return port;
	}
	/**
	 * @param port The port to set.
	 */
	public void setPort(int port) {
		if (!inited) this.port = port;
	}
	
	/**
	 * @return Returns the implClassName.
	 */
	public String getImplClassName() {
		return implClassName;
	}
	/**
	 * @param implClassName The implClassName to set.
	 */
	public void setImplClassName(String implClassName) {
		if (!inited) this.implClassName = implClassName;
	}
	/**
	 * @return Returns the name.
	 */
	public String getName() {
		return name;
	}
	/**
	 * @param name The name to set.
	 */
	public void setName(String name) {
		this.name = name;
		if (name!=null) nameMark = "["+name+"]:";
	}
	/**
	 * @return Returns the maxTryCount.
	 */
	public int getMaxTryCount() {
		return maxTryCount;
	}
	/**
	 * @param maxTryCount The maxTryCount to set.
	 */
	public void setMaxTryCount(int maxTryCount) {
		if (!inited) this.maxTryCount = maxTryCount;
	}
	/**
	 * @return Returns the tryWait.
	 */
	public int getTryWait() {
		return tryWait;
	}
	/**
	 * @param tryWait The tryWait to set.
	 */
	public void setTryWait(int tryWait) {
		if (!inited) this.tryWait = tryWait;
	}
	
	public boolean isNoneCipherMode() {
		return noneCipherMode;
	}
	public void setNoneCipherMode(boolean noneCipherMode) {
		this.noneCipherMode = noneCipherMode;
	}
	
}
